<template>
  <div
    class="el-slider tag-slider"
    :class="{ 'is-vertical': vertical, 'el-slider--with-input': showInput }"
    role="slider"
    :aria-valuemin="min"
    :aria-valuemax="max"
    :aria-orientation="vertical ? 'vertical' : 'horizontal'"
    :aria-disabled="sliderDisabled"
  >
    <el-input-number
      v-model="firstValue"
      v-if="showInput && !range"
      class="el-slider__input"
      ref="input"
      @change="emitChange"
      :step="step"
      :disabled="sliderDisabled"
      :controls="showInputControls"
      :min="min"
      :max="max"
      :debounce="debounce"
      :size="inputSize"
    >
    </el-input-number>
    <div
      class="el-slider__runway"
      :class="{ 'show-input': showInput, disabled: sliderDisabled }"
      :style="runwayStyle"
      @click="onSliderClick"
      ref="slider"
    >
      <div class="el-slider__begin" :style="beginStyle"><span class="tag-slider-tips">预警</span></div>
      <div class="el-slider__bar" :style="barStyle"><span class="tag-slider-tips">良好</span></div>
      <div class="el-slider__end" :style="endStyle"><span class="tag-slider-tips">优秀</span></div>
      <slider-button :vertical="vertical" v-model="firstValue" :tooltip-class="tooltipClass" ref="button1">
      </slider-button>
      <slider-button
        :vertical="vertical"
        v-model="secondValue"
        :tooltip-class="tooltipClass"
        ref="button2"
        v-if="range"
      >
      </slider-button>
      <template v-if="showStops">
        <div class="el-slider__stop" v-for="(item, key) in stops" :key="key" :style="getStopStyle(item)"></div>
      </template>
      <template v-if="markList.length > 0">
        <div>
          <div
            v-for="(item, key) in markList"
            :style="getStopStyle(item.position)"
            class="el-slider__stop el-slider__marks-stop"
            :key="key"
          ></div>
        </div>
        <div class="el-slider__marks">
          <div
            class="el-slider__marks-text"
            v-for="(item, key) in markList"
            :key="key"
            :style="getStopStyle(item.position)"
          >
            {{ typeof item.mark === 'string' ? item.mark : item.mark.label }}
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<script type="text/babel">
import ElInputNumber from 'element-ui/packages/input-number'
import SliderButton from './button.vue'
import Emitter from 'element-ui/src/mixins/emitter'

export default {
  name: 'ElSlider',

  mixins: [Emitter],

  inject: {
    elForm: {
      default: ''
    }
  },

  props: {
    min: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 100
    },
    step: {
      type: Number,
      default: 1
    },
    value: {
      type: [Number, Array],
      default: 0
    },
    showInput: {
      type: Boolean,
      default: false
    },
    showInputControls: {
      type: Boolean,
      default: true
    },
    inputSize: {
      type: String,
      default: 'small'
    },
    showStops: {
      type: Boolean,
      default: false
    },
    showTooltip: {
      type: Boolean,
      default: true
    },
    formatTooltip: Function,
    disabled: {
      type: Boolean,
      default: false
    },
    range: {
      type: Boolean,
      default: false
    },
    vertical: {
      type: Boolean,
      default: false
    },
    height: {
      type: String
    },
    debounce: {
      type: Number,
      default: 300
    },
    label: {
      type: String
    },
    tooltipClass: String,
    marks: Object
  },

  components: {
    ElInputNumber,
    SliderButton
  },

  data() {
    return {
      firstValue: null,
      secondValue: null,
      oldValue: null,
      dragging: false,
      sliderSize: 1
    }
  },

  watch: {
    value(val, oldVal) {
      if (
        this.dragging ||
        (Array.isArray(val) && Array.isArray(oldVal) && val.every((item, index) => item === oldVal[index]))
      ) {
        return
      }
      this.setValues()
    },

    dragging(val) {
      if (!val) {
        this.setValues()
      }
    },

    firstValue(val) {
      if (this.range) {
        this.$emit('input', [this.minValue, this.maxValue])
      } else {
        this.$emit('input', val)
      }
    },

    secondValue() {
      if (this.range) {
        this.$emit('input', [this.minValue, this.maxValue])
      }
    },

    min() {
      this.setValues()
    },

    max() {
      this.setValues()
    }
  },

  methods: {
    valueChanged() {
      if (this.range) {
        return ![this.minValue, this.maxValue].every((item, index) => item === this.oldValue[index])
      } else {
        return this.value !== this.oldValue
      }
    },
    setValues() {
      if (this.min > this.max) {
        console.error('[Element Error][Slider]min should not be greater than max.')
        return
      }
      const val = this.value
      if (this.range && Array.isArray(val)) {
        if (val[1] < this.min) {
          this.$emit('input', [this.min, this.min])
        } else if (val[0] > this.max) {
          this.$emit('input', [this.max, this.max])
        } else if (val[0] < this.min) {
          this.$emit('input', [this.min, val[1]])
        } else if (val[1] > this.max) {
          this.$emit('input', [val[0], this.max])
        } else {
          this.firstValue = val[0]
          this.secondValue = val[1]
          if (this.valueChanged()) {
            this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue])
            this.oldValue = val.slice()
          }
        }
      } else if (!this.range && typeof val === 'number' && !isNaN(val)) {
        if (val < this.min) {
          this.$emit('input', this.min)
        } else if (val > this.max) {
          this.$emit('input', this.max)
        } else {
          this.firstValue = val
          if (this.valueChanged()) {
            this.dispatch('ElFormItem', 'el.form.change', val)
            this.oldValue = val
          }
        }
      }
    },

    setPosition(percent) {
      const targetValue = this.min + (percent * (this.max - this.min)) / 100
      if (!this.range) {
        this.$refs.button1.setPosition(percent)
        return
      }
      let button
      if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
        button = this.firstValue < this.secondValue ? 'button1' : 'button2'
      } else {
        button = this.firstValue > this.secondValue ? 'button1' : 'button2'
      }
      this.$refs[button].setPosition(percent)
    },

    onSliderClick(event) {
      if (this.sliderDisabled || this.dragging) return
      this.resetSize()
      if (this.vertical) {
        const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom
        this.setPosition(((sliderOffsetBottom - event.clientY) / this.sliderSize) * 100)
      } else {
        const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left
        this.setPosition(((event.clientX - sliderOffsetLeft) / this.sliderSize) * 100)
      }
      this.emitChange()
    },

    resetSize() {
      if (this.$refs.slider) {
        this.sliderSize = this.$refs.slider[`client${this.vertical ? 'Height' : 'Width'}`]
      }
    },

    emitChange() {
      this.$nextTick(() => {
        this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value)
      })
    },

    getStopStyle(position) {
      let style = this.vertical ? { bottom: position + '%' } : { left: position + '%' }
      const maxPosition = this.max == 1 ? this.max * 100 : this.max
      if (
        position == 0 ||
        position / maxPosition == this.firstValue ||
        position / maxPosition == this.secondValue ||
        position == maxPosition
      ) {
        style.fontSize = '16px'
        style.fontWeight = 'bold'
      }
      return style
    }
  },

  computed: {
    stops() {
      if (!this.showStops || this.min > this.max) return []
      if (this.step === 0) {
        process.env.NODE_ENV !== 'production' && console.warn('[Element Warn][Slider]step should not be 0.')
        return []
      }
      const stopCount = (this.max - this.min) / this.step
      const stepWidth = (100 * this.step) / (this.max - this.min)
      const result = []
      for (let i = 1; i < stopCount; i++) {
        result.push(i * stepWidth)
      }
      if (this.range) {
        return result.filter(step => {
          return (
            step < (100 * (this.minValue - this.min)) / (this.max - this.min) ||
            step > (100 * (this.maxValue - this.min)) / (this.max - this.min)
          )
        })
      } else {
        return result.filter(step => step > (100 * (this.firstValue - this.min)) / (this.max - this.min))
      }
    },

    markList() {
      if (!this.marks) {
        return []
      }

      const marksKeys = Object.keys(this.marks)
      return marksKeys
        .map(parseFloat)
        .sort((a, b) => a - b)
        .filter(point => point <= this.max && point >= this.min)
        .map(point => ({
          point,
          position: ((point - this.min) * 100) / (this.max - this.min),
          mark: this.marks[point]
        }))
    },

    minValue() {
      return Math.min(this.firstValue, this.secondValue)
    },

    maxValue() {
      return Math.max(this.firstValue, this.secondValue)
    },

    barSize() {
      return this.range
        ? `${(100 * (this.maxValue - this.minValue)) / (this.max - this.min)}%`
        : `${(100 * (this.firstValue - this.min)) / (this.max - this.min)}%`
    },

    barStart() {
      return this.range ? `${(100 * (this.minValue - this.min)) / (this.max - this.min)}%` : '0%'
    },

    barEnd() {
      return this.range ? `${(100 * (this.max - this.maxValue)) / (this.max - this.min)}%` : '100%'
    },

    precision() {
      let precisions = [this.min, this.max, this.step].map(item => {
        let decimal = ('' + item).split('.')[1]
        return decimal ? decimal.length : 0
      })
      return Math.max.apply(null, precisions)
    },

    runwayStyle() {
      return this.vertical ? { height: this.height } : {}
    },

    beginStyle() {
      return this.vertical
        ? {
            height: this.barStart,
            bottom: 0
          }
        : {
            left: 0,
            width: this.barStart
          }
    },

    barStyle() {
      return this.vertical
        ? {
            height: this.barSize,
            bottom: this.barStart
          }
        : {
            width: this.barSize,
            left: this.barStart
          }
    },

    endStyle() {
      return this.vertical
        ? {
            top: 0,
            width: this.barEnd
          }
        : {
            width: this.barEnd,
            right: 0
          }
    },

    sliderDisabled() {
      return this.disabled || (this.elForm || {}).disabled
    }
  },

  mounted() {
    let valuetext
    if (this.range) {
      if (Array.isArray(this.value)) {
        this.firstValue = Math.max(this.min, this.value[0])
        this.secondValue = Math.min(this.max, this.value[1])
      } else {
        this.firstValue = this.min
        this.secondValue = this.max
      }
      this.oldValue = [this.firstValue, this.secondValue]
      valuetext = `${this.firstValue}-${this.secondValue}`
    } else {
      if (typeof this.value !== 'number' || isNaN(this.value)) {
        this.firstValue = this.min
      } else {
        this.firstValue = Math.min(this.max, Math.max(this.min, this.value))
      }
      this.oldValue = this.firstValue
      valuetext = this.firstValue
    }
    this.$el.setAttribute('aria-valuetext', valuetext)

    // label screen reader
    this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`)

    this.resetSize()
    window.addEventListener('resize', this.resetSize)
  },

  beforeDestroy() {
    window.removeEventListener('resize', this.resetSize)
  }
}
</script>

<style lang="scss">
.tag-slider {
  .el-slider__begin,
  .el-slider__end {
    height: 6px;
    position: absolute;
  }
  .el-slider__begin {
    background-color: #f98c6a;
    .tag-slider-tips {
      color: #f98c6a;
    }
    &:before {
      width: 4px;
      height: 12px;
      content: '';
      border-radius: 2px;
      background-color: #f98c6a;
      position: absolute;
      left: -2px;
      bottom: 0;
    }
  }
  .el-slider__bar {
    background-color: #54c6e2 !important;
    .tag-slider-tips {
      color: #54c6e2;
    }
  }
  .el-slider__end {
    background-color: #65d496;
    .tag-slider-tips {
      color: #65d496;
    }
    &:before {
      width: 4px;
      height: 12px;
      content: '';
      border-radius: 2px;
      background-color: #65d496;
      position: absolute;
      right: -2px;
      bottom: 0;
    }
  }
  .tag-slider-tips {
    font-weight: bold;
    margin-top: 20px;
    white-space: nowrap;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
  }
  .el-slider__marks-text {
    margin-top: 0;
  }
  .el-slider__stop {
    width: 3px;
    height: 3px;
    bottom: 1.5px;
  }
}
</style>
